package ppl.tut1

abstract class Expr {

   //override toString method
   override def toString = {
      this match {
         case v: Var    => v.name
         case n: Number => n.num.toString
         case uOP: UnOp => "(" + uOP.op + uOP.arg.toString + ")"
         case bOP: BinOp => {
            bOP.left match {
               case bOP1: BinOp => {
                  bOP.right match {
                     case bOP2: BinOp => "(" + bOP.left.toString + ")" + bOP.op + "(" + bOP.right.toString + ")"
                     case _           => "(" + bOP.left.toString + ")" + bOP.op + bOP.right.toString
                  }

               }
               case _ => {

                  bOP.right match {
                     case bOP2: BinOp => bOP.left.toString + bOP.op + "(" + bOP.right.toString + ")"
                     case _           => bOP.left.toString + bOP.op + bOP.right.toString
                  }
               }
            }
         }
      }

   }

   //define eval method
   def eval(): Double = {
      this match {
         case n: Number => n.num
         case u: UnOp => {
            u.op match {
               case "+" => {
                  u.arg.eval()
               }
               case "-" => {
                  u.arg.eval() * (-1)
               }
               case _ => error("cannot evaluate this Expr")
            }
         }
         case b: BinOp => {
            b.op match {
               case "+" => {
                  b.left.eval() + b.right.eval()
               }
               case "-" => {
                  b.left.eval() - b.right.eval()
               }
               case "*" => {
                  b.left.eval() * b.right.eval()
               }
               case "/" => {
                  val rightEval = b.right.eval()
                  rightEval match {
                     case 0.0 => error("--->Error Devide By Zero")
                     case _   => b.left.eval() / rightEval
                  }
               }
               case _ => error("Operation Unknown")
            }
         }
         case _ => error("Cannot Evaluate this Expr")
      }

   }
}

case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(op: String, arg: Expr) extends Expr
case class BinOp(op: String, left: Expr, right: Expr) extends Expr

object Expr {
   def main(args: Array[String]): Unit = {
      val ex = BinOp("*", Number(4.0), BinOp("+", Var("x"), Number(12.0)))
      val ex1 = BinOp("+", BinOp("*", Number(5), Var("x")), BinOp("/", Number(12), Var("y")))
      println(ex.toString)
      println(ex1.toString)

      val t1 = BinOp("+", Number(4), Number(5))
      println(t1 + " = " + t1.eval())
      val t2 = BinOp("/", Number(3), Number(30))
      println(t2 + " = " + t2.eval())
      val t3 = BinOp("+", Number(3), BinOp("*", Number(2), Number(6)))
      println(t3 + " = " + t3.eval())
   }
}